﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;

#if WPF
using System.Windows;
using Silverlight.Hosting.Wpf.Interop;
using Silverlight.Hosting.Wpf.Private;
using EventArgs = System.Windows.RoutedEventArgs;

namespace Silverlight.Hosting.Wpf
#else
using Silverlight.Hosting.Interop;
using Silverlight.Hosting.Private;

namespace Silverlight.Hosting
#endif
{
    /// <summary>
    /// The Content sub-object of a SilverlightHost.
    /// </summary>
    public sealed class Content
    {
        internal SafeComObject<object> _comObject;
        private Dictionary<string, object> _config;
        private IHostContainerServicesProvider _host;
#if WPF
        private readonly static Type _ownerType = typeof(Content);
#endif

        internal Content(SafeComObject<object> comObject, Dictionary<string, object> config, IHostContainerServicesProvider host)
        {
            _comObject = comObject;
            _config = config;
            _host = host;
        }

#if WPF
        #region Dependency Properties

        public static readonly DependencyPropertyKey ActualHeightPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
            "ActualHeight", typeof(double), _ownerType, new PropertyMetadata(0.0));
        public static readonly DependencyProperty ActualHeightProperty = ActualHeightPropertyKey.DependencyProperty;

        public static readonly DependencyPropertyKey ActualWidthPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
            "ActualWidth", typeof(double), _ownerType, new PropertyMetadata(0.0));
        public static readonly DependencyProperty ActualWidthProperty = ActualWidthPropertyKey.DependencyProperty;

        public static readonly DependencyProperty IsFullScreenProperty = DependencyProperty.RegisterAttached(
            "IsFullScreen", typeof(bool), _ownerType, new PropertyMetadata(false, OnIsFullScreenChanged));

        public static readonly RoutedEvent LoadedEvent = EventManager.RegisterRoutedEvent(
            "Loaded", RoutingStrategy.Bubble, typeof(RoutedEventHandler), _ownerType);

        public static readonly RoutedEvent ErrorEvent = EventManager.RegisterRoutedEvent(
            "Error", RoutingStrategy.Bubble, typeof(RoutedEventHandler<ErrorEventArgs>), _ownerType);

        public static readonly RoutedEvent ZoomedEvent = EventManager.RegisterRoutedEvent(
            "Zoomed", RoutingStrategy.Bubble, typeof(RoutedEventHandler), _ownerType);

        public static readonly RoutedEvent ResizedEvent = EventManager.RegisterRoutedEvent(
            "Resized", RoutingStrategy.Bubble, typeof(RoutedEventHandler), _ownerType);

        public static readonly RoutedEvent FullScreenChangedEvent = EventManager.RegisterRoutedEvent(
            "FullScreenChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), _ownerType);

        #endregion
#endif

        #region Public Events

        /// <summary>
        /// Raised after the content in the Silverlight plug-in has completely loaded.
        /// </summary>
#if WPF
        public event RoutedEventHandler Loaded
        {
            add { AddLoadedHandler(_host.Host as DependencyObject, value); }
            remove { RemoveLoadedHandler(_host.Host as DependencyObject, value); }
        }
#else
        public event EventHandler Loaded;
#endif

        /// <summary>
        /// Raised when when the Silverlight plug-in generates a XAML parse error or
        /// run-time error at the native-code level.
        /// </summary>
#if WPF
        public event RoutedEventHandler<ErrorEventArgs> Error
        {
            add { AddErrorHandler(_host.Host as DependencyObject, value); }
            remove { RemoveErrorHandler(_host.Host as DependencyObject, value); }
        }
#else
        public event EventHandler<ErrorEventArgs> Error;
#endif

        /// <summary>
        /// Raised when the Silverlight plug-in content area receives a host-generated zoom event.
        /// </summary>
#if WPF
        public event RoutedEventHandler Zoomed
        {
            add { AddZoomedHandler(_host.Host as DependencyObject, value); }
            remove { RemoveZoomedHandler(_host.Host as DependencyObject, value); }
        }
#else
        public event EventHandler Zoomed;
#endif

        /// <summary>
        /// Raised when the ActualHeight or ActualWidth of the Silverlight plug-in change.
        /// </summary>
#if WPF
        public event RoutedEventHandler Resized
        {
            add { AddResizedHandler(_host.Host as DependencyObject, value); }
            remove { RemoveResizedHandler(_host.Host as DependencyObject, value); }
        }
#else
        public event EventHandler Resized;
#endif

        /// <summary>
        /// Raised whenever the FullScreen property of the Silverlight plug-in changes.
        /// </summary>
#if WPF
        public event RoutedEventHandler FullScreenChanged
        {
            add { AddFullScreenChangedHandler(_host.Host as DependencyObject, value); }
            remove { RemoveFullScreenChangedHandler(_host.Host as DependencyObject, value); }
        }
#else
        public event EventHandler FullScreenChanged;
#endif

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the height of the Silverlight plug-in content area.
        /// </summary>
        public double ActualHeight
        {
            get { return (double)(uint)GetPropertyInternal("ActualHeight"); }
        }

        /// <summary>
        /// Gets the widthof the Silverlight plug-in content area.
        /// </summary>
        public double ActualWidth
        {
            get { return (double)(uint)GetPropertyInternal("ActualWidth"); }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether the Silverlight plug-in is displaying in full-screen mode.
        /// </summary>
        public bool IsFullScreen
        {
            get { return (bool)GetPropertyInternal("FullScreen"); }
            set { SetPropertyInternal("FullScreen", value); }
        }

        #endregion

        #region Public Methods

#if WPF
        internal static void SetActualHeight(DependencyObject d, double value)
        {
            d.SetValue(ActualHeightPropertyKey, value);
        }

        public static double GetActualHeight(DependencyObject d)
        {
            return (double)d.GetValue(ActualHeightProperty);
        }

        internal static void SetActualWidth(DependencyObject d, double value)
        {
            d.SetValue(ActualWidthPropertyKey, value);
        }

        public static double GetActualWidth(DependencyObject d)
        {
            return (double)d.GetValue(ActualWidthProperty);
        }

        public static void SetIsFullScreen(DependencyObject d, bool value)
        {
            d.SetValue(IsFullScreenProperty, value);
        }

        public static bool GetIsFullScreen(DependencyObject d)
        {
            return (bool)d.GetValue(IsFullScreenProperty);
        }

        public static void AddLoadedHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.AddHandler(LoadedEvent, handler);
            }
        }

        public static void RemoveLoadedHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.RemoveHandler(LoadedEvent, handler);
            }
        }

        public static void AddErrorHandler(DependencyObject d, RoutedEventHandler<ErrorEventArgs> handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.AddHandler(ErrorEvent, handler);
            }
        }

        public static void RemoveErrorHandler(DependencyObject d, RoutedEventHandler<ErrorEventArgs> handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.RemoveHandler(ErrorEvent, handler);
            }
        }

        public static void AddZoomedHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.AddHandler(ZoomedEvent, handler);
            }
        }

        public static void RemoveZoomedHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.RemoveHandler(ZoomedEvent, handler);
            }
        }

        public static void AddResizedHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.AddHandler(ResizedEvent, handler);
            }
        }

        public static void RemoveResizedHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.RemoveHandler(ResizedEvent, handler);
            }
        }

        public static void AddFullScreenChangedHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.AddHandler(FullScreenChangedEvent, handler);
            }
        }

        public static void RemoveFullScreenChangedHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement host = d as UIElement;
            if (host != null)
            {
                host.RemoveHandler(FullScreenChangedEvent, handler);
            }
        }

#endif

        /// <summary>
        /// Creates XAML content dynamically.
        /// </summary>
        /// <param name="xamlContent">The XAML content to add to the existing Silverlight object hierarchy.</param>
        /// <returns>An object reference if the XAML content was successfully created; otherwise, null.</returns>
        public SafeComObject<object> CreateFromXaml(string xamlContent)
        {
            if (xamlContent == null) throw new ArgumentNullException("xamlContent");
            if (xamlContent == string.Empty) throw new ArgumentException("Empty argument not allowed.", "xamlContent");

            object ret = InvokeMethodInternal("CreateFromXaml", xamlContent);
            if (ret != null) return new SafeComObject<object>(ret);
            else return null;
        }

        /// <summary>
        /// Creates XAML content dynamically.
        /// </summary>
        /// <param name="xamlContent">The XAML content to add to the existing Silverlight object hierarchy.</param>
        /// <param name="createNameScope">Determines whether to create x:Name references in XAML content that do not
        /// conflict with other named elements. The createNameScope parameter is optional, and its value defaults to
        /// false if it is not specified.</param>
        /// <returns>An object reference if the XAML content was successfully created; otherwise, null.</returns>
        public SafeComObject<object> CreateFromXaml(string xamlContent, bool createNameScope)
        {
            if (xamlContent == null) throw new ArgumentNullException("xamlContent");
            if (xamlContent == string.Empty) throw new ArgumentException("Empty argument not allowed.", "xamlContent");

            object ret = InvokeMethodInternal("CreateFromXaml", xamlContent, createNameScope);
            if (ret != null) return new SafeComObject<object>(ret);
            else return null;
        }

        /// <summary>
        /// Creates XAML content dynamically by using downloader content.
        /// </summary>
        /// <param name="downloader">The Downloader object that initiated the request for the downloaded content.</param>
        /// <param name="part">The name of the specific part of the downloaded content package. When the downloaded content
        /// package is a .zip file, part refers to the contents of a file name in downloader. If the downloaded content
        /// does not represent packaged content, set part to an empty string.</param>
        /// <returns>An object reference if the XAML content was successfully created; otherwise, null.</returns>
        public SafeComObject<object> CreateFromXamlDownloader(SafeComObject<IXcpObject> downloader, string part)
        {
            if (downloader == null) throw new ArgumentNullException("downloader");

            object ret = InvokeMethodInternal("CreateFromXamlDownloader", downloader.Object, part ?? string.Empty);
            if (ret != null) return new SafeComObject<object>(ret);
            else return null;        
        }

        /// <summary>
        /// Gets any named object in the Silverlight object hierarchy by referencing the object's x:Name or Name attribute value.
        /// </summary>
        /// <param name="objectName">The name of the object to get.</param>
        /// <returns>A reference to the specified object if the object was successfully found; otherwise, null.</returns>
        public SafeComObject<object> FindName(string objectName)
        {
            if (objectName == null) throw new ArgumentNullException("objectName");
            if (objectName == string.Empty) throw new ArgumentException("Empty argument not allowed.", "objectName");

            object ret = InvokeMethodInternal("FindName", objectName);
            if (ret != null) return new SafeComObject<object>(ret);
            else return null;
        }

        /// <summary>
        /// Gets the specified property of the Content object.
        /// </summary>
        public object GetProperty(string name)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (name == string.Empty) throw new ArgumentException("Empty argument not allowed.", "name");

            if (_comObject == null)
            {
                throw new AxHost.InvalidActiveXStateException(name, AxHost.ActiveXInvokeKind.PropertyGet);
            }
            else
            {
                return GetPropertyInternal(name);
            }
        }

        /// <summary>
        /// Sets the specified property of the Content object.
        /// </summary>
        public void SetProperty(string name, object value)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (name == string.Empty) throw new ArgumentException("Empty argument not allowed.", "name");
            
            if (_comObject == null)
            {
                throw new AxHost.InvalidActiveXStateException(name, AxHost.ActiveXInvokeKind.PropertySet);
            }
            else
            {
                SetPropertyInternal(name, value);
            }
        }
        
        /// <summary>
        /// Calls the specified method on the Content object.
        /// </summary>
        /// <returns></returns>
        public object InvokeMethod(string name, params object[] args)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (name == string.Empty) throw new ArgumentException("Empty argument not allowed.", "name");
            
            if (_comObject == null)
            {
                throw new AxHost.InvalidActiveXStateException(name, AxHost.ActiveXInvokeKind.MethodInvoke);
            }
            else
            {
                return InvokeMethodInternal(name, args);
            }
        }

        #endregion

        #region Internal Methods

        internal void OnLoaded(object sender, EventArgs e)
        {
#if WPF
            e.RoutedEvent = LoadedEvent;
            e.Source = _host.Host;
            (_host.Host as UIElement).RaiseEvent(e);
#else
            if (Loaded != null)
            {
                Loaded(sender, e);
            }
#endif
        }

        internal void OnError(object sender, ErrorEventArgs e)
        {
#if WPF
            e.RoutedEvent = ErrorEvent;
            e.Source = _host.Host;
            (_host.Host as UIElement).RaiseEvent(e);
#else
            if (Error != null)
            {
                Error(sender, e);
            }
#endif
        }

        internal void OnZoomed(object sender, EventArgs e)
        {
#if WPF
            e.RoutedEvent = ZoomedEvent;
            e.Source = _host.Host;
            (_host.Host as UIElement).RaiseEvent(e);
#else
            if (Zoomed != null)
            {
                Zoomed(sender, e);
            }
#endif
        }

        internal void OnResized(object sender, EventArgs e)
        {
#if WPF
            e.RoutedEvent = ResizedEvent;
            e.Source = _host.Host;
            (_host.Host as UIElement).RaiseEvent(e);
#else
            if (Resized != null)
            {
                Resized(sender, e);
            }
#endif
        }

        internal void OnFullScreenChanged(object sender, EventArgs e)
        {
#if WPF
            e.RoutedEvent = FullScreenChangedEvent;
            e.Source = _host.Host;
            (_host.Host as UIElement).RaiseEvent(e);
#else
            if (FullScreenChanged != null)
            {
                FullScreenChanged(sender, e);
            }
#endif
        }

        #endregion

        #region Private Methods

        private object GetPropertyInternal(string name)
        {
            if (_comObject == null)
            {
                if (_config.ContainsKey(name)) return _config[name];
                else throw new AxHost.InvalidActiveXStateException(name, AxHost.ActiveXInvokeKind.PropertyGet);
            }
            else
            {
                return _comObject.Object.GetType().InvokeMember(name, BindingFlags.GetProperty, null, _comObject.Object, null);
            }
        }

        private void SetPropertyInternal(string name, object value)
        {
            if (_comObject == null)
            {
                _config[name] = value;
            }
            else
            {
                _comObject.Object.GetType().InvokeMember(name, BindingFlags.SetProperty, null, _comObject.Object, new object[] { value });
            }
        }

        private object InvokeMethodInternal(string name, params object[] args)
        {
            if (_comObject == null)
            {
                throw new AxHost.InvalidActiveXStateException(name, AxHost.ActiveXInvokeKind.MethodInvoke);
            }
            else
            {
                return _comObject.Object.GetType().InvokeMember(name, BindingFlags.InvokeMethod, null, _comObject.Object, args);
            }
        }

#if WPF

        private static void OnIsFullScreenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is ISilverlightHost)
            {
                (d as ISilverlightHost).Content.IsFullScreen = (bool)e.NewValue;
            }
        }

#endif

        #endregion
    }
}
